#include "talk/examples/crossfirewall/cflogin.h"
#include "talk/base/logging.h"

#include <algorithm>
#include <time.h>

#ifdef POSIX 
#include <stdint.h>
#include <unistd.h>
#else
#include <iomanip>
#include <Winbase.h>
#include <tchar.h>
#include <atlstr.h>
#include <WtsApi32.h>
#include <winsvc.h>
#pragma comment(lib, "WtsApi32.lib")
#endif


//#ifdef _DEBUG
#	define  _XMPP_MSG_DBG
//#else
//#	undef _XMPP_MSG_DBG
//#endif
//#undef _XMPP_MSG_DBG

#ifdef _XMPP_MSG_DBG
class DebugLog : public sigslot::has_slots<> {
public:
	DebugLog() :
	  debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
		  debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
		  censor_password_(false)
	  {}
	  char * debug_input_buf_;
	  int debug_input_len_;
	  int debug_input_alloc_;
	  char * debug_output_buf_;
	  int debug_output_len_;
	  int debug_output_alloc_;
	  bool censor_password_;

	  void Input(const char * data, int len) {
		  if (debug_input_len_ + len > debug_input_alloc_) {
			  char * old_buf = debug_input_buf_;
			  debug_input_alloc_ = 4096;
			  while (debug_input_alloc_ < debug_input_len_ + len) {
				  debug_input_alloc_ *= 2;
			  }
			  debug_input_buf_ = new char[debug_input_alloc_];
			  memcpy(debug_input_buf_, old_buf, debug_input_len_);
			  delete[] old_buf;
		  }
		  memcpy(debug_input_buf_ + debug_input_len_, data, len);
		  debug_input_len_ += len;
		  DebugPrint(debug_input_buf_, &debug_input_len_, false);
	  }

	  void Output(const char * data, int len) {
		  if (debug_output_len_ + len > debug_output_alloc_) {
			  char * old_buf = debug_output_buf_;
			  debug_output_alloc_ = 4096;
			  while (debug_output_alloc_ < debug_output_len_ + len) {
				  debug_output_alloc_ *= 2;
			  }
			  debug_output_buf_ = new char[debug_output_alloc_];
			  memcpy(debug_output_buf_, old_buf, debug_output_len_);
			  delete[] old_buf;
		  }
		  memcpy(debug_output_buf_ + debug_output_len_, data, len);
		  debug_output_len_ += len;
		  DebugPrint(debug_output_buf_, &debug_output_len_, true);
	  }

	  static bool
		  IsAuthTag(const char * str, size_t len) {
			  if (str[0] == '<' && str[1] == 'a' &&
				  str[2] == 'u' &&
				  str[3] == 't' &&
				  str[4] == 'h' &&
				  str[5] <= ' ') {
					  std::string tag(str, len);

					  if (tag.find("mechanism") != std::string::npos)
						  return true;

			  }
			  return false;
	  }

	  void
		  DebugPrint(char * buf, int * plen, bool output) {
			  int len = *plen;
			  if (len > 0) {
				  time_t tim = time(NULL);
				  struct tm * now = localtime(&tim);
				  char *time_string = asctime(now);
				  if (time_string) {
					  size_t time_len = strlen(time_string);
					  if (time_len > 0) {
						  time_string[time_len-1] = 0;    // trim off terminating \n
					  }
				  }
				  LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<<<<<<<<<<")
					  << " : " << time_string<<" Length:"<<*plen;
				  if ( *plen<10 )
				  {
					  char bbf[1024];
					  char *p = bbf;
					  int i;
					  *p = 0;
					  for ( i=0;i<*plen;++i )
					  {
						  p += sprintf(p,"%02X ",buf[i]);
					  }
					  *p = 0;
					  char tmp = buf[*plen];
					  buf[*plen] = 0;
					  LOG(INFO)<<"Detail "<<bbf<<std::endl<<buf;
					  buf[*plen] = tmp;
				  }
				  bool indent;
				  int start = 0, nest = 3;
				  for (int i = 0; i < len; i += 1) {
					  if (buf[i] == '>') {
						  if ((i > 0) && (buf[i-1] == '/')) {
							  indent = false;
						  } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
							  indent = false;
							  nest -= 2;
						  } else {
							  indent = true;
						  }

						  // Output a tag
						  LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i + 1 - start);

						  if (indent)
							  nest += 2;

						  // Note if it's a PLAIN auth tag
						  if (IsAuthTag(buf + start, i + 1 - start)) {
							  censor_password_ = true;
						  }

						  // incr
						  start = i + 1;
					  }

					  if (buf[i] == '<' && start < i) {
						  if (censor_password_) {
							  LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
							  censor_password_ = false;
						  }
						  else {
							  LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i - start);
						  }
						  start = i;
					  }
				  }
				  len = len - start;
				  memcpy(buf, buf + start, len);
				  *plen = len;
			  }
	  }

};

static DebugLog debug_log_;
#endif

namespace cricket {

	void* startLogin(void* param)
	{
		CFLogin* login = (CFLogin*)param;
		login->LoginStart();
		return NULL;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////////////////////
	bool CFLogin::Login(const CFUsrInfo &usrinfo)
	{
		if(!CheckLoginState() && !CheckJidValid(usrinfo)) return false;

		login_retrys_ = 0;
		InitLoginParameter();

#ifdef WIN32
		DWORD dwID;
		login_tid_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startLogin, this, CREATE_SUSPENDED, &dwID);
		ResumeThread(login_tid_);
#else
		pthread_create(&login_tid_, NULL, startLogin, this);
#endif
		return true;
	}

	bool CFLogin::LoginRetry()
	{
		if(!CheckLoginState())  return false;
		login_retrys_ ++;

#ifdef WIN32
		DWORD dwID;
		login_tid_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startLogin, this, CREATE_SUSPENDED, &dwID);
		ResumeThread(login_tid_);
#else
		pthread_create(&login_tid_, NULL, startLogin, this);
#endif
		return true;
	}

	bool CFLogin::CheckLoginState()
	{
		CritScope cs(&loginstate_crit_);
		if(login_state_ != 0)
			return false;

		login_state_ = 1;
		return true;	
	}

	bool CFLogin::CheckJidValid(const CFUsrInfo& usrinfo)
	{
		// check jid valid
		login_jid_ = buzz::Jid(usrinfo.usrname);
		if(!login_jid_.IsValid() || login_jid_.node() == ""){
			{
				CritScope cs(&loginstate_crit_);
				login_state_ = 0;
			}
			return false;
		}

		login_usrinfo_ = usrinfo;
		return true;
	}

	void CFLogin::InitLoginParameter()
	{
		talk_base::InitializeSSL();

		login_xcs_.set_user(login_jid_.node());
		login_xcs_.set_host(login_jid_.domain());

		// set xmpp client password
		talk_base::InsecureCryptStringImpl pass;
		pass.password() = login_usrinfo_.usrpass;
		login_xcs_.set_pass(talk_base::CryptString(pass));

		// set xmpp client server
		login_xcs_.set_server(talk_base::SocketAddress("talk.google.com", 5222));
		//login_xcs_.set_server(talk_base::SocketAddress("talk.google.com", 443));

		// set xmpp client login type
		if(login_usrinfo_.logintype == 1){
			login_xcs_.set_use_tls(false);
			login_xcs_.set_allow_plain(true);
		}
		else  login_xcs_.set_use_tls(true);

		// set xmpp client login resource
		login_xcs_.set_resource(login_usrinfo_.resource);
	}

	void CFLogin::LoginStart()
	{
		login_thread_ = new Thread;
		talk_base::ThreadManager::SetCurrent(login_thread_);

		xmpp_pump_ = new XmppPump();
		xmpp_client_ = xmpp_pump_->client();
		xmpp_client_->SignalStateChange.connect(this, &CFLogin::OnLoginStateChange);

#ifdef _XMPP_MSG_DBG
    talk_base::LogMessage::LogToDebug(0);
	xmpp_pump_->client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
    xmpp_pump_->client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
#endif

		login_thread_->PostDelayed(LOGIN_BOMB_TIME_OUT, cmd_console_, CMD_LOGIN_BOMB_TIMEOUT);
		xmpp_socket_ = new XmppSocket(true);
		xmpp_pump_->DoLogin(login_xcs_, xmpp_socket_, NULL);
		{
			talk_base::CritScope cs(&loginstate_crit_);
			login_state_ = 2;
		}
		login_thread_->Run();
		{
			talk_base::CritScope cs(&loginstate_crit_);
			login_state_ = 0;
		}

		{
			talk_base::CritScope cs(&login_crit_);
			if(xmpp_pump_)    { delete xmpp_pump_;    xmpp_pump_    = NULL; }
			if(xmpp_socket_)  { xmpp_socket_ = NULL; }
			if(login_thread_) { delete login_thread_; login_thread_ = NULL; }
		}
	}

	bool CFLogin::Logout()
	{
		talk_base::CritScope cs(&logout_crit_);
		int retry = 0;

		{
			talk_base::CritScope cs(&loginstate_crit_);
			if(login_state_ == 0)  return true;
			else if(login_state_ == 1)  retry = 5;
		}
		while(retry > 0){
#ifdef WIN32
			Sleep(50);
#else
			usleep(50000);
#endif
			{
				talk_base::CritScope cs(&loginstate_crit_);
				if(login_state_ == 2)  break;
			}

			if(--retry == 0) return false;
		}

		{
			talk_base::CritScope cs(&login_crit_);
			if(login_thread_){
				xmpp_pump_->DoDisconnect();
				login_thread_->Stop();
			}
		}

		if(login_tid_ != NULL){
#ifdef WIN32
			WaitForSingleObject(login_tid_, INFINITE);
			CloseHandle(login_tid_);
#else
			void *pv;
			pthread_join(login_tid_, &pv);
#endif
			login_tid_ = NULL;
		}
		return true;
	}

	void CFLogin::OnLoginStateChange(buzz::XmppEngine::State state)
	{
		if(state == buzz::XmppEngine::STATE_OPEN){
			login_thread_->Post(cmd_console_, CMD_LOGIN_INIT_CORE);
			cmd_thread_->Post(cmd_console_, CMD_LOGIN_STATE_CHANGE, new LoginState(state, buzz::XmppEngine::ERROR_NONE));
			CancelLoginBomb();
			InitLoginTask();
			PingStart();
		}
		else if(state == buzz::XmppEngine::STATE_CLOSED){
			buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
			login_thread_->Post(cmd_console_, CMD_LOGIN_UNINIT_CORE);
			cmd_thread_->Post(cmd_console_, CMD_LOGIN_STATE_CHANGE, new LoginState(state, error));
			PingStop();
		}
	}

	void CFLogin::OnPresenceStatusUpdate(const buzz::Status& status)
	{
		cmd_thread_->Post(cmd_console_, CMD_LOGIN_ROSTER_CHANGE,
			new RosterItem(status.jid().Str(), status.status(), status.version(), status.show()));
	}

	void CFLogin::InitLoginTask()
	{
		std::string client_unique = xmpp_client_->jid().Str();
		talk_base::InitRandom(client_unique.c_str(), client_unique.size());

#if defined(HTTPALLOCATOR)
		jingle_info_task_ = new buzz::JingleInfoTask(xmpp_client_);
		jingle_info_task_->RefreshJingleInfoNow();
		jingle_info_task_->SignalJingleInfo.connect(this, &CFLogin::OnJingleInfo);
		jingle_info_task_->SignalOFInfo.connect(this, &CFLogin::OnOFInfo);
		jingle_info_task_->Start();
#endif
		InitUsrStatusTask();
		//InitDevStatusTask();
	}

	void CFLogin::InitUsrStatusTask()
	{
		presence_push_task_ = new buzz::PresencePushTask(xmpp_client_);
		presence_push_task_->SignalStatusUpdate.connect(this, &CFLogin::OnPresenceStatusUpdate);
		presence_push_task_->Start();

		status_usr_.set_jid      (xmpp_client_->jid());
		status_usr_.set_available(true);
		status_usr_.set_show	 (buzz::Status::SHOW_DND);
		status_usr_.set_status   ("using libjingle...");
		status_usr_.set_priority ( 0 );

		status_usr_.set_know_capabilities (true);
		status_usr_.set_control_capability(true);
		status_usr_.set_str_capability    (true);
		status_usr_.set_video_capability  (true);
		status_usr_.set_sound_capability  (true);
		status_usr_.set_is_google_client  (true);
		status_usr_.set_version(login_usrinfo_.computername);

		presence_out_task_ = new buzz::PresenceOutTask(xmpp_client_);
		presence_out_task_->Send(status_usr_);
		presence_out_task_->Start();
	}

	bool CFLogin::Ping()
	{
		if(mgr_ping_.queryState () != HEART_BEAT_STARTED){
			printf("#####  Detected Ping Stopped\n");
			return false;
		}

		if(login_thread_){
			str::CXMLSenderTask *pSender = new str::CXMLSenderTask(xmpp_client_, login_usrinfo_.computername, str::PACKAGE_TYPE_PING);
			pSender->SendToXmppServer(  );
			pSender->Start();
			login_thread_->PostDelayed(PING_INTERVAL, cmd_console_, CMD_LOGIN_DO_PING);
			return true;
		}
		return false;
	}

	void CFLogin::PingStart()
	{
		if(mgr_ping_.queryState() == HEART_BEAT_STOPPED){
			printf("#### Ping  Loop  Started\n");
			mgr_ping_.setState(HEART_BEAT_STARTED);
			login_thread_->PostDelayed(PING_INTERVAL, cmd_console_, CMD_LOGIN_DO_PING);
		}
		else  printf("####ERROR Ping Already Started !!!!\n");
	}

	void CFLogin::PingStop()
	{
		if(mgr_ping_.queryState() == HEART_BEAT_STARTED){
			printf("#### Ping Message Loop Stopped\n");
			mgr_ping_.setState(HEART_BEAT_STOPPED);
		}
		else  printf("####ERROR Ping Already Stopped !!!!\n");
	}

	void CFLogin::OnJingleInfo(const std::string & relay_token, const std::vector<std::string> &relay_addresses, const std::vector<talk_base::SocketAddress> &stun_addresses)
	{
		SignalJingleInfo(relay_token, relay_addresses, stun_addresses);
	}

	void CFLogin::OnOFInfo(const std::string &publicIP, const std::vector<talk_base::SocketAddress> &tcpStuns)
	{	
		SignalOFInfo(publicIP, tcpStuns);
	}
}